﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProgNetComponentsDx.Components;
using System.Diagnostics;
using System.Xml;
using System.ComponentModel;
using System.IO;
using System.Drawing;
using System.Windows.Forms;

namespace BuildHelper.BHTasks
{
    public abstract class BaseTask
    {
        protected BaseTask()
        {
            Errors = new List<string>();
            ShowOperationInProgressMessage = true;
            ShowInfoAlert = true;
        }

        #region Static
        protected static int AlertSeq;
        protected static string UniqueAlertId
        {
            get { return "BaseTaskAlert" + AlertSeq++; }
        }
        public static BaseTask DeserializeTask(string xml)
        {
            BaseProcesTask t = null;
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                var main = doc.SelectSingleElement("Main");
                string type = main.GetAttributeValue("Type");
                Type taskType = Type.GetType(type);
                t = Activator.CreateInstance(taskType) as BaseProcesTask;
                if (t != null)
                    t.ParamsFromString(xml);
            }
            catch { }
            return t;
        }
        #endregion

        #region Events
        public event EventHandler TaskFinished;
        #endregion

        #region Fields
        protected List<string> Alerts = new List<string>();
        protected DateTime dtStart = DateTime.MinValue;
        private int _Progress;
        #endregion

        #region Properties
        public int MaxItems { get; set; }
        public int Progress
        {
            get { return _Progress; }
            set
            {
                _Progress = value;
                if (MaxItems > 0)
                    UpdateTaskInfo();
            }
        }
        public Image AlertImage { get; set; }
        public DevExpress.XtraEditors.MarqueeProgressBarControl ProgressBar { get; set; }
        public MemoBgAppender MemoAppender { get; set; }
        public FileBgAppender FileAppender { get; set; }
        public string AlertInfoId { get; private set; }
        public TaskStatus Status { get; private set; }
        public List<string> Errors { get; private set; }
        public bool IsRunning { get; protected set; }
        public bool ShowOperationInProgressMessage { get; set; }
        public bool AutoCreateFile { get; set; }

        [SerializeProperty]
        public bool ShowInfoAlert { get; set; }

        
        #endregion

        #region Methods
        protected void UpdateTaskInfo()
        {
            UpdateAlertInfo();
            UpdateProgress();
        }
        protected void OnTaskFinished()
        {
            if (TaskFinished != null)
                TaskFinished(this, EventArgs.Empty);
        }
        public void ShowLogFile()
        {
            if (FileAppender != null)
            {
                GlobalTools.FilesTools.RunFile(FileAppender.FileName);
            }
        }
        #region public methods
        public virtual void Cancel()
        {
            if (IsRunning)
            {
                EndTask(TaskStatus.Canceled);
            }
        }

        public virtual void Run()
        {
            if (BeginTask())
            {
                RunTask();
                StartStopProgress(true);
                if (ShowInfoAlert)
                    this.ShowAlert();
            }
        }
        #endregion
        #region serialize/ deserialize
        public string SerializeTask()
        {
            return ParamsToString();
        }
        protected virtual string ParamsToString()
        {
            var properties = System.ComponentModel.TypeDescriptor.GetProperties(this, new Attribute[] { SerializePropertyAttribute.Default });
            XmlDocument doc = new XmlDocument();
            var main = doc.CreateElement("Main");
            main.AppendAttribute("Type", this.GetType().FullName);
            doc.AppendChild(main);

            if (properties != null)
            {
                foreach (System.ComponentModel.PropertyDescriptor p in properties)
                {
                    if (p.Attributes.OfType<SerializePropertyAttribute>().Any())
                    {
                        string val = null;
                        val = PropertyToString(p);
                        var el = main.AppendChildElement(p.Name, val);
                        if (p.PropertyType == typeof(string) && p.GetValue(this) != null && val.IsNullOrEmpty())
                            el.AppendAttribute("IsEmptyString", true);
                    }
                }
            }
            return doc.ToFormatedString();
        }
        protected virtual string PropertyToString(PropertyDescriptor p)
        {
            return p.GetValue(this).AsString();
        }
        protected virtual void ParamsFromString(string str)
        {
            var properties = System.ComponentModel.TypeDescriptor.GetProperties(this, new Attribute[] { SerializePropertyAttribute.Default });
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(str);
            var main = doc.SelectSingleElement("Main");
            foreach (PropertyDescriptor p in properties)
            {
                if (p.Attributes.OfType<SerializePropertyAttribute>().Any())
                {
                    var el = main.SelectSingleElement(p.Name);
                    if (el != null)
                    {
                        XmlNode valEl = el.FirstChild;
                        if (valEl != null && valEl is XmlText)
                        {
                            object value = GetPropertyValue(p, valEl.Value);
                            if (p.PropertyType == typeof(string) && el.GetAttributeValue("IsEmptyString").AsBool() && value.IsNull())
                                value = string.Empty;

                            p.SetValue(this, value);
                        }
                    }
                }
            }
        }
        protected virtual object GetPropertyValue(PropertyDescriptor p, string val)
        {
            if (val != null)
            {
                Type type = p.PropertyType;
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && type.GetGenericArguments() != null && type.GetGenericArguments().Length == 1)
                {
                    type = type.GetGenericArguments()[0];
                }
                return Convert.ChangeType(val, type);
            }
            return null;
        }
        #endregion
        #region Alert
        public void CloseAlert()
        {
            FormMain.CloseAlertById(AlertInfoId);
        }
        public void ShowAlert()
        {
            if (AlertInfoId.IsNotNull())
            {
                if (!FormMain.IsAlertShown(AlertInfoId))
                {
                    if (AlertImage != null)
                        FormMain.ShowAlert(GetAlertText(), AlertImage, new FormMain.AlertInfo { Id = AlertInfoId, ShouldStay = true });
                    else
                        FormMain.ShowAlert(GetAlertText(), AlertType.Info, new FormMain.AlertInfo { Id = AlertInfoId, ShouldStay = true });
                }
            }
        }

        protected void UpdateAlertInfo()
        {
            UpdateAlertInfo(GetAlertText());
        }
        protected void UpdateAlertInfo(string msg)
        {
            FormMain.UpdateAlertText(AlertInfoId, msg);
        }
        protected virtual string GetAlertText()
        {
            string str = GetTaskName();
            if (MaxItems > 0)
                str = str.AppendLine(Progress + "/" + MaxItems);
            return str;
        }
        #endregion
        #region Progress bar
        protected void UpdateProgress()
        {
            SetProgress(GetProgressText());
        }
        protected virtual string GetProgressText()
        {
            return GetAlertText().Replace(Constants.NewLineLinux, ";  ");
        }
        public virtual void SetProgress(string text)
        {
            if (ProgressBar != null)
            {
                if (ProgressBar.InvokeRequired)
                {
                    ProgressBar.Invoke(new Action<string>(this.SetProgress), text);
                    return;
                }
                ProgressBar.Text = text;
            }
        }
        protected void StartStopProgress(bool start)
        {
            if (ProgressBar != null)
            {
                if (ProgressBar.InvokeRequired)
                {
                    ProgressBar.Invoke(new Action<bool>(this.StartStopProgress), start);
                    return;
                }
                ProgressBar.Properties.Stopped = !start;
                if (!start)
                    ProgressBar.Text = string.Empty;
            }
        }
        #endregion
        #endregion

        #region Abstract
        protected abstract string GetTaskName();
        protected abstract void RunTask();
        #endregion
        
        protected virtual void EndTask(TaskStatus status)
        {
            if (IsRunning)
            {
                FinalizeTask(status);
                IsRunning = false;
            }
        }
        
        protected virtual bool BeginTask()
        {
            bool result = false;
            if (!IsRunning)
            {
                InitTaskParams();
                result = true;
                IsRunning = true;
            }
            else if (ShowOperationInProgressMessage)
            {
                FormMain.Instance.ShowDxErrorDialog("Operacja w toku");
            }
            return result;
        }

        protected virtual void InitTaskParams()
        {
            foreach (string s in Alerts)
                FormMain.CloseAlertById(s);

            Alerts.Clear();
            CloseAlert();

            AlertInfoId = UniqueAlertId;

            Status = TaskStatus.Running;
            dtStart = DateTime.Now;

            Errors.Clear();

            if (AutoCreateFile)
            {
                if (FileAppender == null)
                    FileAppender = new FileBgAppender();
                FileAppender.FileName = GlobalTools.FilesTools.GetTemplLogFile(GetTaskName());
            }
            if (FileAppender != null)
            {
                "".WriteToFile(FileAppender.FileName, writeLine: false);
                FileAppender.Start();
            }
            if (MemoAppender != null && MemoAppender.Memo != null)
            {
                if (MemoAppender.Memo.InvokeRequired)
                {
                    MemoAppender.Memo.Invoke(new Action(() => { MemoAppender.Memo.Text = string.Empty; }));
                }
                else
                {
                    MemoAppender.Memo.Text = string.Empty;
                }
                MemoAppender.Start();
            }
        }
        protected virtual void FinalizeTask(TaskStatus status)
        {
            if (MemoAppender != null)
                MemoAppender.Append("Całkowity czas: " + (DateTime.Now - dtStart).ToString(@"hh\:mm\:ss"));

            if (MemoAppender != null)
                MemoAppender.Finish();
            if (FileAppender != null)
                FileAppender.Finish();

            StartStopProgress(false);
            if (Status == TaskStatus.Running)
            {
                Status = status;
                if (ShowInfoAlert)
                {
                    string id = UniqueAlertId;
                    Alerts.Add(id);
                    if (status == TaskStatus.Canceled)
                        FormMain.ShowWarningAlert("Zadanie " + GetTaskName() + " anulowane", id);
                    else if (status == TaskStatus.CanceledWithError)
                        FormMain.ShowErrorAlert("Zadanie " + GetTaskName() + " anulowane z powodu błędów", id);
                    else if (status == TaskStatus.Finished)
                    {
                        FormMain.ShowInfoAlert("Zadanie " + GetTaskName() + " zakończone", id);
                        CloseAlert();
                    }
                    else if (status == TaskStatus.FinishedWithError)
                    {
                        FormMain.ShowWarningAlert("Zadanie " + GetTaskName() + " zakończone z błędami", id);
                        CloseAlert();
                    }
                }
            }
            OnTaskFinished();
        }

        #region MemoAppender
        protected virtual bool ShouldAddMsgToMemo(string msg)
        {
            return true;
        }
        protected virtual string GetMsgForMemo(string msg)
        {
            return msg;
        }
        #endregion

        #region FileAppender
        protected virtual bool ShouldAddMsgToFile(string msg)
        {
            return true;
        }
        protected virtual string GetMsgForFile(string msg)
        {
            return msg;
        }
        #endregion
    }

    public abstract class BaseProcesTask : BaseTask
    {       
        public Process Process
        {
            get;
            private set;
        }

        [SerializeProperty]
        public bool StopOnFirstError { get; set; }

        protected override void InitTaskParams()
        {
            base.InitTaskParams();
            Process = GlobalTools.CreateCMDProcess(ProcessMessage);
        }
        protected override void FinalizeTask(TaskStatus status)
        {
            base.FinalizeTask(status);
            if (Process != null)
            {
                try { Process.KillProcessTree(true); }
                catch { }
                try { Process.Dispose(); }
                catch { }
                Process = null;
            }
        }
        protected abstract void RunTask(Process process);
        protected override void RunTask()
        {
            RunTask(Process);
            Process.Echo(TaskMessages.TaskFinished);
        }
        #region Process Message
        protected virtual bool IsError(string msg)
        {
            return false;
        }
        protected virtual void ProcessError(string msg)
        {
            Errors.Add(msg);
            if (StopOnFirstError)
                EndTask(TaskStatus.CanceledWithError);
        }

        protected virtual void ProcessMessage(object sender, DataReceivedEventArgs e)
        {
            if (Process != null)
            {
                string msg = e.Data.AsString();

                if (MemoAppender != null && ShouldAddMsgToMemo(msg))
                    MemoAppender.Append(GetMsgForMemo(msg));

                if (FileAppender != null && ShouldAddMsgToFile(msg))
                    FileAppender.Append(GetMsgForFile(msg));

                ProcessMessage(msg);
                
                if (IsError(msg))
                    ProcessError(msg);

                if (msg == TaskMessages.TaskFinished)
                {
                    if (Errors.Count > 0)
                        EndTask(TaskStatus.FinishedWithError);
                    else
                        EndTask(TaskStatus.Finished);
                }
            }
        }
        protected virtual void ProcessMessage(string msg)
        {

        }

        #endregion

        

    }

    [AttributeUsage(AttributeTargets.Property)]
    public class SerializePropertyAttribute : Attribute
    {
        public static SerializePropertyAttribute Default = new SerializePropertyAttribute();
    }
}
